/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */
package sk.jp.commons.utils;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 * Provides usefull methods for string processing.
 * 
 * @author Jan Pastor 
 */
public class StringUtils {

    /**
     * String value type.
     * <p>
     * Allowed characters: all.
     */
    public static final int TYPE_ANY = 0;

    /**
     * String value type.
     * <p>
     * Allowed characters: {A...Z}: only in capital letters.
     */
    public static final int TYPE_ALPHA = 1;

    /**
     * String value type.
     * <p>
     * Allowed characters: {0...9): numeral code.
     */
    public static final int TYPE_NUM = 2;

    /**
     * String value type.
     * <p>
     * Allowed characters: {A...Z} ? {0...9}: capital letters and numeral code.
     */
    public static final int TYPE_ALPHANUM = 3;

    /**
     * String value type.
     * <p>
     * Allowed characters: {A...Z} ? {“}: capital letters and blanks.
     */
    public static final int TYPE_ALPHABLANK = 4;

    /**
     * String value type.
     * <p>
     * Allowed characters: {A...Z} ? {0...9} ? {.,-()/='+:? blank}: capital
     * letters, numeral code, TELEX characters and blanks.
     */
    public static final int TYPE_ALPHANUMBLANK = 5;

    /**
     * String value type.
     * <p>
     * Allowed characters: {A...Z} ? {'/-+.? blank}.
     */
    public static final int TYPE_NAME = 6;

    /**
     * String value type.
     * <p>
     * Allowed characters: YYYYMMDD year, month, day.
     * <p>
     * Allowed characters: {0...9), length 8 chars.
     */
    public static final int TYPE_DATE = 7;

    /**
     * String value type.
     * <p>
     * Allowed characters: YYYYMMDDhhmmss year, month, day, hour, minute,
     * second.
     * <p>
     * Allowed characters: {0...9), length 14 chars.
     */
    public static final int TYPE_DATETIME = 8;

    /**
     * String value type.
     * <p>
     * Allowed characters: element out of list, where element length is x.
     */
    public static final int TYPE_CODE = 9;

    // ----------------------------------------------------------------------

    private StringUtils() {
    }

    // ----------------------------------------------------------------------

    /**
     * Returns true if given string can be parsed into <code>Integer</code>.
     * @param s string that may be containing integer number.
     * @return true if parsing to <code>Integer</code> was successful, false in
     *         other case.
     */
    public static boolean isInteger(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (Exception e) {
            // ignored
            return false;
        }
    }

    /**
     * Returns true if the string object is null or empty.
     * @param s string to check.
     * @return true if s is null or empty; false in other case.
     */
    public static boolean isNullOrEmpty(String s) {
        return s == null || "".equals(s);
    }

    /**
     * Validate <code>password</code>.
     * <p>
     * See description of method <code>isValidString</code>.
     * @param password
     * @param minLen minimal count of characters. Validation is true if
     *            <code>password.length()</code> &gt;= <code>minLen</code>.
     * @param maxLen maximal count of characters. Validation is true if
     *            <code>password.length()</code> &lt;= <code>maxLen</code>.
     * @param contNum when true, <code>ctr</code> must contains one or more
     *            numeric character.
     * @param contUpp when true, <code>ctr</code> must contains one or more
     *            uppercase character.
     * @param contLow when true, <code>ctr</code> must contains one or more
     *            lowercase character.
     * @return true if <code>password</code> doesn't contains whitespace
     *         character and is valid for all rules.
     */
    public static boolean isValidPassword(String password, int minLen, int maxLen, boolean contNum,
            boolean contUpp, boolean contLow) {
        return isValidString(password, minLen, maxLen, false, contNum, contUpp, contLow);
    }

    /**
     * Validate <code>name</code>.
     * <p>
     * See description of method <code>isValidString</code>.
     * <p>
     * Validation is extendet to rule, where <code>name</code> mus not contains
     * non-letter characters (e.g. '!', ' ', '-', ...).
     * <p>
     * Valid name (result true) is, when given <code>name</code> contains only
     * characters from set {'a'-'z', 'A'-Z', '0'-'9', '_'} and has length
     * greater to <code>minLen</code> (inclusive) and smaller to
     * <code>maxLen</code> inclusive.
     * @param name
     * @param minLen minimal count of characters. Validation is true if
     *            <code>name.length()</code> &gt;= <code>minLen</code>.
     * @param maxLen maximal count of characters. Validation is true if
     *            <code>name.length()</code> &lt;= <code>maxLen</code>.
     * @return true if <code>password</code> doesn't contains whitespace
     *         character, contais characters only from set {'a'-'z', 'A'-Z',
     *         '0'-'9', '_'} and is valid for all rules.
     */
    public static boolean isValidName(String name, int minLen, int maxLen) {
        boolean s = isValidString(name, minLen, maxLen, false, false, false, false);
        if (!s) {
            return false;
        }
        //
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (!('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' || c == '_')) {
                return false;
            }
        }
        return true;
    }

    /**
     * Validate string.
     * <p>
     * Returns true if given <code>str</code>ing satisfy all given rules.
     * @param str string for validation.
     * @param minLen minimal count of characters. Validation is true if
     *            <code>str.length()</code> &gt;= <code>minLen</code>.
     * @param maxLen maximal count of characters. Validation is true if
     *            <code>str.length()</code> &lt;= <code>maxLen</code>.
     * @param contWhite when true, any character of <code>str</code> <b>may
     *            be</b> <i>whitespace</i> character. Validation is false if
     *            <code>Character.isWhitespace(str.charAt(i)) && !contWhite</code>
     *            , true in other cases.
     * @param contNum when true, <code>ctr</code> must contains one or more
     *            numeric character.
     * @param contUpp when true, <code>ctr</code> must contains one or more
     *            uppercase character.
     * @param contLow when true, <code>ctr</code> must contains one or more
     *            lowercase character.
     * @return true, if validation is successful for all rules.
     */
    public static boolean isValidString(String str, int minLen, int maxLen, boolean contWhite,
            boolean contNum, boolean contUpp, boolean contLow) {
        if (str == null) {
            throw new NullPointerException("Parameter str is null");
        }
        if (str.length() < minLen || str.length() > maxLen) {
            return false;
        }
        //
        boolean contWhiteResult = true;
        boolean contNumResult = false;
        boolean contUppResult = false;
        boolean contLowResult = false;
        //
        if (!contNum)
            contNumResult = true;
        if (!contUpp)
            contUppResult = true;
        if (!contLow)
            contLowResult = true;
        //
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (Character.isWhitespace(c) && !contWhite) {
                contWhiteResult = false;
            }
            if (Character.isDigit(c)) {
                contNumResult = true;
            }
            if (Character.isUpperCase(c)) {
                contUppResult = true;
            }
            if (Character.isLowerCase(c)) {
                contLowResult = true;
            }
        }
        //
        return contWhiteResult && contNumResult && contUppResult && contLowResult;
    }

    /**
     * Converts given HTML code to string.
     * <p>
     * Replaces all occurences of '&lt;' (to <i>&amp;lt;</i>), '&gt;' (to
     * <i>&amp;gt;</i>), '\n' (to <i>&lt;br/&gt;</i>) and '\t' (to
     * <i>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;</i>).
     * @param s HTML code to transform.
     * @return result string.
     */
    public static String toHtmlString(String s) {
        String r;
        r = s.replaceAll("<", "&lt;");
        r = r.replaceAll(">", "&gt;");
        r = r.replaceAll("\n", "<br/>");
        r = r.replaceAll("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
        return r;
    }

    /**
     * Converts given HTML code to string.
     * <p>
     * Replaces all occurences of '\n' (to <i>&lt;br/&gt;</i>) and '\t' (to
     * <i>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;</i>).
     * @param s HTML code to transform.
     * @return result string.
     */
    public static String toHtmlString2(String s) {
        String r;
        r = s.replaceAll("\n", "<br/>");
        r = r.replaceAll("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
        return r;
    }

    /**
     * Converts given XML code to string.
     * <p>
     * Replaces all occurences of '&lt;' (to <i>&amp;lt;</i>), '&gt;' (to
     * <i>&amp;gt;</i>) and &amp; (to <i>&amp;amp;</i>).
     * @param s XML code to transform.
     * @return result string.
     */
    public static String toXmlString(String s) {
        String r;
        r = s.replaceAll("<", "&lt;");
        r = r.replaceAll(">", "&gt;");
        r = r.replaceAll("&", "&amp;");
        return r;
    }

    /**
     * Unescapes escaped XML string.
     * <p>
     * Replaces all occurences of '&amp;lt;', '&amp;gt;', '&amp;amp;' with
     * '&lt;', '&gt;' and '&amp;'.
     * @param str escaped XML string.
     * @return XML code.
     */
    public static String unescapeXml(String str) {
        str = str.replaceAll("&lt;", "<");
        str = str.replaceAll("&gt;", ">");
        str = str.replaceAll("&amp;", "&");
        return str;
    }

    /**
     * Replaces all occurences of <code>match</code> in given string
     * <code>s</code>.
     * <p>
     * Value of <code>match</code> isn't compiled to <code>Pattern</code> as in
     * method <code>String.replaceAll</code>.
     * <p>
     * If source string <code>s</code> or <code>match</code> is empty string,
     * returns original source string <code>s</code>.
     * @param match simple pattern for match. No regular expresion logic is
     *            performed.
     * @param replacement
     * @return string where each <code>match</code> substring is replaced to
     *         <code>replacement</code>.
     */
    public static String replaceAll(String s, String match, String replacement) {
        if (s == null)
            throw new NullPointerException("Parameter s is null");
        if (match == null)
            throw new NullPointerException("Parameter match is null");
        if (replacement == null)
            throw new NullPointerException("Parameter replacement is null");
        //
        if ("".equals(s) || "".equals(match)) {
            return s;
        }
        String out = s;
        int matchInx = out.indexOf(match);
        while (matchInx != -1) {
            StringBuilder buf = new StringBuilder(out.substring(0, matchInx));
            buf.append(replacement);
            buf.append(out.substring(matchInx + match.length()));
            out = buf.toString();
            matchInx = out.indexOf(match, matchInx + replacement.length());
        }
        return out;
    }

    /**
     * Replaces diacritic characters (of slovak language) from given string with
     * nondiacritical eqivalents.
     * <p>
     * E.g. 'á', 'ä' will be replaced to 'a'.
     * <p>
     * If given string is null or empty, returns original.
     * @param s string to process.
     * @return result string where all diacritical characters (of slovak
     *         language) are replaced.
     */
    public static String removeDiacriticSK(String s) {
        if (s == null || "".equals(s)) {
            return s;
        }
        String out = s;
        for (int i = 0; i < diacriticTableSK.length; i++) {
            out = out.replace(diacriticTableSK[i][0], diacriticTableSK[i][1]);
        }
        return out;
    }

    /**
     * Map for conversion diacritic characters (common Slovak (<i>SK</i>)
     * diacritic characters) to non-diacritic characters.
     */
    private static char[][] diacriticTableSK = { { '\u00e1', 'a' }, { '\u00e4', 'a' },
            { '\u00c1', 'A' }, { '\u00c4', 'A' }, { '\u010d', 'c' }, { '\u010c', 'C' },
            { '\u010f', 'd' }, { '\u010e', 'D' }, { '\u00e9', 'e' }, { '\u011b', 'e' },
            { '\u00eb', 'e' }, { '\u00c9', 'E' }, { '\u011a', 'E' }, { '\u00cb', 'E' },
            { '\u00ed', 'i' }, { '\u00cd', 'I' }, { '\u013a', 'l' }, { '\u013e', 'l' },
            { '\u0139', 'L' }, { '\u013d', 'L' }, { '\u0148', 'n' }, { '\u0147', 'N' },
            { '\u00f3', 'o' }, { '\u00f4', 'o' }, { '\u0151', 'o' }, { '\u00f6', 'o' },
            { '\u00d3', 'O' }, { '\u00d4', 'O' }, { '\u0150', 'O' }, { '\u00d6', 'O' },
            { '\u0155', 'r' }, { '\u0159', 'r' }, { '\u0154', 'R' }, { '\u0158', 'R' },
            { '\u0161', 's' }, { '\u0160', 'S' }, { '\u0165', 't' }, { '\u0164', 'T' },
            { '\u00fa', 'u' }, { '\u016f', 'u' }, { '\u0171', 'u' }, { '\u00fc', 'u' },
            { '\u00da', 'U' }, { '\u016e', 'U' }, { '\u0170', 'U' }, { '\u00dc', 'U' },
            { '\u00fd', 'y' }, { '\u00dd', 'Y' }, { '\u017e', 'z' }, { '\u017d', 'Z' } };

    /**
     * Converts given string <code>s</code> to string which contains only
     * {'a'-'z', '0'-'9'} characters.
     * <p>
     * Convert each character of string to byte (using <i>UTF-8</i> charset) and
     * then converts this byte to hex string.
     * @param s string to convert.
     * @return encoded string.
     */
    public static String toHexString(String s) {
        if (s == null)
            throw new NullPointerException("Parameter s is null");
        //
        byte[] d2;
        try {
            d2 = s.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Convert string to bytest failed", e);
        }
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < d2.length; i++) {
            int c = d2[i];
            if (d2[i] < 0) {
                c = d2[i] + 127;
            }
            out.append(Integer.toHexString(c));
        }
        return out.toString();
    }

    /**
     * Generate random string with specified length.
     * <p>
     * Result string contains characters from set {a-z, A-Z, 0-9}.
     * <p>
     * This method can be used e.g. for generating random passwords, etc.
     * @param length length of generated string (min 1).
     * @return generated random string.
     */
    public static String randomString(int length) {
        if (length < 1) {
            throw new IllegalArgumentException("Parameter length has invalid value (" + length
                    + ")");
        }
        //
        StringBuilder buf = new StringBuilder(length);
        Random random = new Random(System.currentTimeMillis() / length
                + Thread.currentThread().getName().hashCode());
        for (int i = 0; i < length; i++) {
            int r = -1;
            while (!('a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || '0' <= r && r <= '9')) {
                r = random.nextInt('z' - '0') + '0';
            }
            buf.append((char) r);
        }
        return buf.toString();
    }

    /**
     * Returns a map from supported canonical charset names to charset objects.
     * @return a map from supported canonical charset names to charset objects.
     */
    public static Map supportedCharsets() {
        return Charset.availableCharsets();
    }

    /**
     * Append specified chars to left of given string, to make result string
     * with specified length.
     * <p>
     * If source string is greater or equal length as specified result length,
     * return source string.
     * <p>
     * If source string is null, returns string with fill characters only.
     * @param value source string.
     * @param c character used to fill.
     * @param resultLength required length of result string.
     * @return result string.
     */
    public static String fillString(String value, char c, int resultLength) {
        if (value == null) {
            value = "";
        }
        if (value.length() >= resultLength) {
            return value;
        }
        char[] rsa = new char[resultLength - value.length()];
        Arrays.fill(rsa, c);
        return (new String(rsa)) + value;
    }

    /**
     * Truncate given string to specified length.
     * <p>
     * If length of given string is less like specified maximal length, returns
     * given string.
     * @param s string to truncate. If null, returns null. If empty, returns
     *            empty string.
     * @param maxlen maximal length of result string (must be >= 0).
     * @return truncated (if longer as maximal length) string.
     */
    public static String truncateString(String s, int maxlen) {
        if (maxlen < 0)
            throw new IllegalArgumentException("Invalid value of parameter maxlen (" + maxlen + ")");
        if (s == null || s.length() == 0 || s.length() <= maxlen) {
            return s;
        } else {
            return s.substring(0, maxlen);
        }
    }

    /**
     * Cuts given string to list of substrings, with maximal length
     * <code>maxlen</code>.
     * @param str string to cut.
     * @param maxlen maximal length of substring.
     * @return a list of substrings with specified maximal length. If given
     *         string is empty, returns list with one empty string.
     */
    public static List<String> cutString(String str, int maxlen) {
        if (str == null)
            throw new NullPointerException("Parameter str is null");
        if (maxlen < 0)
            throw new IllegalArgumentException("Invalid value of parameter maxlen (" + maxlen + ")");

        if (str.length() == 0) {
            return Arrays.asList("");
        }

        int len = str.length();
        int startInx;
        int endInx;
        List<String> out = new ArrayList<String>();
        for (int i = 0; i <= len / maxlen; i++) {
            startInx = i * maxlen;
            endInx = (i + 1) * maxlen;
            endInx = endInx > len ? len : endInx;
            if (startInx < endInx) {
                out.add(str.substring(startInx, endInx));
            }
        }
        return out;
    }

    /**
     * Test if given character is alpha character.
     * @param c
     * @return true if c belong from {'A', ..., 'Z'}, else false.
     */
    public static boolean isAlphaChar(char c) {
        return 'A' <= c && c <= 'Z';
    }

    /**
     * Test if given character is decimal number character.
     * @param c
     * @return true if c belong from {'0', ..., '9'}, else false.
     */
    public static boolean isNumChar(char c) {
        return '0' <= c && c <= '9';
    }

    /**
     * Test if given character is blank character.
     * @param c
     * @return true if c equals to ' ' (space), else false.
     */
    public static boolean isBlankChar(char c) {
        return c == ' ';
    }

    /**
     * Test if given character is <i>telex</i> character.
     * @param c
     * @return true if c belong from {'.', ',', '-', '(', ')', '/', '=', ''',
     *         '+', ':', '?'}, else false.
     */
    public static boolean isTelexChar(char c) {
        return c == '.' || c == ',' || c == '-' || c == '(' || c == ')' || c == '/' || c == '='
                || c == '\'' || c == '+' || c == ':' || c == '?';
    }

    /**
     * Validate given value according to specified type and maximum length.
     * @param value value to validate. If null, no validation perfomed.
     * @param type type of value. Use one of <code>TYPE_...</code> constants. If
     *            value contains characters that are not allowed for specified
     *            type, returns false. If you don't want to check type, use
     *            constant <code>TYPE_ANY</code>.
     * @param maxlen maximum of allowed length. If length of value is greater,
     *            returns false. If you don't want to check length, use value
     *            -1.
     */
    public static boolean checkValue(String value, int type, int maxlen) {
        if (value == null) {
            return true;
        }
        // check length:
        if (maxlen > -1 && value.length() > maxlen) {
            return false;
        }
        // check type:
        if (type == TYPE_ANY) {
            return true;
        }
        if (type == TYPE_DATE && value.length() != 8) {
            return false;
        }
        if (type == TYPE_DATETIME && value.length() != 14) {
            return false;
        }
        char[] chars = value.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            switch (type) {
            case TYPE_ALPHA:
                if (!isAlphaChar(c)) {
                    return false;
                }
                break;
            case TYPE_NUM:
                if (!isNumChar(c)) {
                    return false;
                }
                break;
            case TYPE_ALPHANUM:
                if (!(isAlphaChar(c) || isNumChar(c))) {
                    return false;
                }
                break;
            case TYPE_ALPHABLANK:
                if (!(isAlphaChar(c) || isBlankChar(c))) {
                    return false;
                }
                break;
            case TYPE_ALPHANUMBLANK:
                if (!(isAlphaChar(c) || isNumChar(c) || isTelexChar(c) || isBlankChar(c))) {
                    return false;
                }
                break;
            case TYPE_NAME:
                if (!(isAlphaChar(c) || isBlankChar(c) || c == '\'' || c == '/' || c == '-'
                        || c == '+' || c == '.' || c == '?')) {
                    return false;
                }
                break;
            case TYPE_DATE:
            case TYPE_DATETIME:
                if (!isNumChar(c)) {
                    return false;
                }
                break;
            case TYPE_CODE:
                if (Character.isLetter(c) && !isAlphaChar(c)) {
                    return false;
                }
                break;
            default:
                throw new IllegalArgumentException("Unsupported type (" + type + ")");
            }
        }
        return true;
    }

    /**
     * Cleans given string from all redundant spaces (double spaces and spaces
     * at the end of the string)
     * @param s
     * @return string without redundant spaces
     */
    public static String cleanString(String s) {
        while (s.contains("  ")) {
            s = s.replaceAll("  ", " ");
        }
        return s.trim();
    }

    /**
     * Search the given <tt>text</tt> string for the given <tt>word</tt>. (word
     * is char sequence separated with " ")
     * @param text text string to be searched
     * @param word word to be found
     * @param ignoreCase search ignores case if true
     * @return true if the <tt>word</tt> is found in the <tt>text</tt>
     */
    public static boolean containsWord(String text, String word, boolean ignoreCase) {
        if (ignoreCase) {
            text = text.toLowerCase();
            word = word.toLowerCase();
        }
        String[] words = text.split(" ");
        for (String s : words) {
            if (s.equals(word)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns string value of specified object by invoking Object.toString()
     * method. If specified object is null default value is returned.
     * 
     * @param o object to get string value of
     * @param defaulValue string returned if o is null
     * @return toString representation of given object or defaultValue if null
     */
    public static String valueOf(Object o, String defaulValue) {
        return o != null ? o.toString() : defaulValue;
    }
    
    /**
     * Process comma delimited list by mapping each item using specified item mapper.
     * 
     * @param <I> - type of mapped items
     * @param delimitedList - comma delimited string list
     * @param itemMapper - mapper used to map items
     * @return list of mapped items
     * @see StringUtils.ItemMapper<I>
     */
    public static <I> List<I> processCommaDelimitedList(String delimitedList,
                                                        ItemMapper<I> itemMapper) {
        return processDelimitedList(delimitedList, ",", itemMapper);
    }
    
    /**
     * Process delimited list by mapping each item using specified item mapper.
     * 
     * @param <I> - type of mapped items
     * @param delimitedList - delimited string list
     * @param delimiter - delimiter used to split list
     * @param itemMapper - mapper used to map items
     * @return list of mapped items
     * @see StringUtils.ItemMapper<I>
     */
    public static <I> List<I> processDelimitedList(String delimitedList, String delimiter,
                                                   ItemMapper<I> itemMapper) {
        if(delimitedList == null)
            throw new IllegalArgumentException("Delimited list must not be 'null'.");
        if(delimiter == null)
            throw new IllegalArgumentException("Delimiter must not be 'null'.");
        if(itemMapper == null)
            throw new IllegalArgumentException("Item mapper must be not 'null'.");
        
        if("".equals(delimitedList))
            return new ArrayList<I>();
        
        String[] tokens = delimitedList.split(delimiter);

        List<I> items = new ArrayList<I>(tokens.length);
        for (int i = 0; i < tokens.length; i++) {
            I item = itemMapper.mapItem(tokens[i], i);
            items.add(item);
        }

        return items;
    }

    /**
     * An interface used for mapping delimited list of items into objects.
     * 
     * @param <I> mapped item type
     */
    public static interface ItemMapper<I> {

        I mapItem(String item, int index);
        
    }

    /**
     * Compares two strings lexicographically and returns the result following
     * contract of the {@link Comparable} interface implemented in
     * {@link String#compareToIgnoreCase(String)}. Handles null parameters. Null
     * parameter is always less than notnull. (So if string1 == null and string2
     * is not, method will return -1.)
     * @param string1
     * @param string2
     * @return the value <code>0</code> if the string2 is equal to string1 (also
     *         when both are null); a value less than <code>0</code> if string1
     *         is lexicographically less than string2 (or when string1 is null);
     *         and a value greater than <code>0</code> if string1 is
     *         lexicographically greater than string2 (or strging2 is null).
     */
    public static int compareStrings(String string1, String string2) {
        int compare;
        if(string1 == null) {
            if (string2 == null) {
                compare = 0;
            } else {
                compare = -1;
            }
        } else if (string2 == null){
            compare = 1;
        } else {
            compare = string1.compareTo(string2);
        }
        return compare;
    }
    
    /**
     * Compares two strings lexicographically ignoring case and returns the
     * result following contract of the {@link Comparable} interface implemented
     * in {@link String#compareToIgnoreCase(String)}. Handles null parameters.
     * Null parameter is always less than notnull. (So if string1 == null and
     * string2 is not, method will return -1.)
     * @param string1
     * @param string2
     * @return the value <code>0</code> if the string2 is equal to string1 (also
     *         when both are null); a value less than <code>0</code> if string1
     *         is lexicographically less than string2 (or when string1 is null);
     *         and a value greater than <code>0</code> if string1 is
     *         lexicographically greater than string2 (or strging2 is null).
     */
    public static int compareStringsIgnoreCase(String string1, String string2) {
        int compare;
        if(string1 == null) {
            if (string2 == null) {
                compare = 0;
            } else {
                compare = -1;
            }
        } else if (string2 == null){
            compare = 1;
        } else {
            compare = string1.compareToIgnoreCase(string2);
        }
        return compare;
    }

    /**
     * Blank characters.
     */
    public static Set<Character> SPLIT_BLANK = Collections.unmodifiableSet(
        new HashSet<Character>(Arrays.asList(
            ' ', '\t', '\n')));
    /**
     * Blank and sentence characters.
     */
    public static Set<Character> SPLIT_SENTENCE = Collections.unmodifiableSet(
        new HashSet<Character>(Arrays.asList(
            ' ', '\t', '\n', '.', '?', '!', ',', ';')));
    /**
     * Blank, sentence ant other <i>not words or numeric</i> character.
     */
    public static Set<Character> SPLIT_EXT = Collections.unmodifiableSet(
        new HashSet<Character>(Arrays.asList(
            ' ', '\t', '\n', '.', ',', ';', ':', '=', '/', '\\', '+', '-', '&', '|')));

    /**
     * Wraps lines of given text.
     * <p>Wraps lines that are longer as <code>lineMinLength</code> on nearest one of
     * <code>splitters</code>
     * character. If line is longer as <code>lineMaxLength</code>, wraps it immediately.
     * Wrapping process equals to insert specified string <code>lineBreak</code> to end
     * of wrapped line (splitter character remain on end of line), except if
     * <code>lineBreak</code> string starts with found splitter character.
     * @param text source text. If is null, returns null.
     * @param lineMinLength minimal length (characters) of wrapped line. Must be
     * lesser than <code>lineMaxLength </code> and greater than 0.
     * @param lineMaxLength maximal length (characters) linit of line.
     * @param splitters array of splitter characters that signs end of wrapped line.
     * You can use predefined characters {@link #SPLIT_BLANK},
     * {@link #SPLIT_SENTENCE}, {@link #SPLIT_EXT}. Can't be null or
     * empty array.
     * @param lineBreak string that breaks wrapped line, e.g. '<i>\n</i>' or
     * '<i>&lt;br/&gt;</i>'. If null, uses "\n".
     * @return wrapped text.
     * @deprecated You can use better method for wrapping, that support more precise
     *  wrapping - {@link #wrapText(String, int, Set, String)}.
     */
    public static String wrapText2(String text, int lineMinLength, int lineMaxLength,
                                  Set<Character> splitters, String lineBreak){
        if(splitters == null) throw new NullPointerException("Parameter splitters is null");
        if(splitters.size() == 0) throw new IllegalArgumentException("Empty set of spliter characters");
        if(lineMinLength < 1) throw new IllegalArgumentException("Minimal length of line can't be lesser than 1");
        if(lineMinLength > lineMaxLength) throw new IllegalArgumentException("Minimal length of line can't be greater than maximal limit of line length");

        if(text == null){
            return text;
        }
        if(lineBreak == null){
            lineBreak = "\n";
        }

        char lineBreakStart = lineBreak.length() == 1 ? lineBreak.charAt(0) : 0;
        char[] chars = text.toCharArray();
        StringBuilder out = new StringBuilder();
        int lc = 0;
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            out.append(c);
            lc++;
            if(lineBreakStart != 0 && c == lineBreakStart){
                lc = 0; //line is broken by c
            }
            if(lc > lineMinLength){
                if(splitters.contains(c) || lc >= lineMaxLength){
                    if(lineBreakStart == 0 || c != lineBreakStart){
                        //append breaker only if c is not breaker too
                        out.append(lineBreak);
                    }
                    lc = 0;
                }
            }
        }
        return out.toString();
    }

    /**
     * Wraps lines of given text.
     * <p>Wraps lines that are longer as <code>lineMinLength</code> on nearest one of
     * <code>splitters</code>
     * character. If line is longer as <code>lineMaxLength</code>, wraps it immediately.
     * Wrapping process equals to insert specified string <code>lineBreak</code> to end
     * of wrapped line (splitter character remain on end of line), except if
     * <code>lineBreak</code> string starts with found splitter character.
     * @param text source text. If is null, returns null.
     * @param lineMaxLength maximal length (characters) limit of wrapped line.
     * @param splitters array of splitter characters that signs end of wrapped line.
     * You can use predefined characters {@link #SPLIT_BLANK},
     * {@link #SPLIT_SENTENCE}, {@link #SPLIT_EXT}. Can't be null or
     * empty array.
     * @param lineBreak string that breaks wrapped line, e.g. '<i>\n</i>' or
     * '<i>&lt;br/&gt;</i>'. If null, uses "\n".
     * @return wrapped text.
     */
    public static String wrapText(String text, int lineMaxLength,
                                  Set<Character> splitters, String lineBreak){
        if(lineMaxLength < 1) throw new IllegalArgumentException("Parameter lineMaxLength must be greater than zero");
        if(splitters == null) throw new NullPointerException("Parameter splitters is null");
        if(splitters.size() == 0) throw new IllegalArgumentException("Empty set of spliter characters");

        if(text == null){
            return text;
        }
        if(lineBreak == null){
            lineBreak = "\n";
        }

        char lineBreakStart = lineBreak.length() > 0 ? lineBreak.charAt(0) : 0;
        char[] chars = text.toCharArray();
        StringBuilder out = new StringBuilder(chars.length);

        int textLength = text.length();
        int inxLs = 0;

        while(inxLs < textLength){
            int inxLe = inxLs + lineMaxLength > textLength
                        ? textLength : inxLs + lineMaxLength;

            String sub = text.substring(inxLs, inxLe);
            char[] subChars = sub.toCharArray();

            //Scan in sub for existing breakers
            if(!"".equals(lineBreak)){
                int inxLb = sub.lastIndexOf(lineBreak);
                if(inxLb > 0){
                    out.append(sub.substring(0, inxLb)).append(lineBreak);
                    inxLs = inxLs + inxLb + 1;
                    continue;
                }
            }

            boolean splitterNotFound = true;

            for (int i = subChars.length - 1; i >= 0; i--) {
                char subChar = subChars[i];

                if(textLength > inxLe){
                    char c = text.charAt(inxLe);
                    if(lineBreak.length() == 1 && c == lineBreak.charAt(0)){
                        //if char after end of sub equals to lineBreak, append over sub + lineBreak and continue
                        out.append(sub).append(c);
                        inxLs = inxLe + 1;
                        splitterNotFound = false;
                        break;
                    }
                }

                if(splitters.contains(subChar)){
                    //splitter char found
                    String subSub = sub.substring(0, i + 1);
                    out.append(subSub);

                    if(lineBreakStart == 0 || subChar != lineBreakStart){
                        //if splitter is not lineBreaker
                        if(i == subChars.length - 1 && textLength > inxLe){
                            //if on the end and there are following chars:
                            char c = text.charAt(inxLe);
                            if(lineBreakStart != 0 && c != lineBreakStart){
                                //check next char if can append lineBreak
                                out.append(lineBreak);
                                inxLs = inxLe;
                            }else{
                                //do not append lineBreak, but c and skip
                                out.append(c);
                                inxLs = inxLe + 1;
                            }
                        }else{
                            if(textLength > inxLe){
                                //else append lineBreak only if text continue after splitter
                                out.append(lineBreak);
                            }
                            inxLs = inxLs + i + 1;
                        }
                    }else{
                        //else if splitter if lineBreak, do not append lineBreak, only skip
                        inxLs = inxLs + i + 1;
                    }
                    splitterNotFound = false;
                    break;
                }
            }
            if(splitterNotFound){
                out.append(sub);   //append all sub
                if(textLength > inxLe){
                    //if text follows after inxLe, check next char c
                    char c = text.charAt(inxLe);
                    if(lineBreakStart == 0 || c != lineBreakStart){
                        //if c isn't breaker, append breaker
                        out.append(lineBreak);
                        inxLs = inxLe;
                    }else{
                        //else append c and skip it
                        out.append(c);
                        inxLs = inxLe + 1;
                    }
                }else{
                    inxLs = inxLe;
                }
            }
        }
        return out.toString();
    }

}
